/*
 * Copyright 2011 Philippe Blanc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package me.l1k3.fx.client.engine.impl;

import me.l1k3.core.client.Core;
import me.l1k3.fx.client.channel.inter.Channel;
import me.l1k3.fx.client.channel.inter.ChannelCSS3;
import me.l1k3.fx.client.channel.inter.ChannelContext;
import me.l1k3.fx.client.engine.easing.*;
import me.l1k3.fx.client.engine.inter.FXEngine;
import me.l1k3.fx.client.engine.inter.FXEngineConnector;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.animation.client.AnimationScheduler;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.regexp.shared.SplitResult;

public class FXEngineJS extends Animation implements FXEngine {
    private FXEngineConnector fx;
	private Easing easing;
	private int delay;
    private int duration;
    private JavaScriptObject handleFire;
	private Scheduler.ScheduledCommand display;
	private Scheduler.ScheduledCommand fire;
	
    public FXEngineJS() {
        super();
    }
    
    public FXEngineJS(AnimationScheduler scheduler) {
        super(scheduler);
    }
	
	@Override
	public void setFX(FXEngineConnector fx) {
		this.fx = fx;
	}
	
	@Override
    public void unsetFX(FXEngineConnector fx) {
	    if(this.fx==fx) {
	        this.fx = null;
	        handleFire = null;
	        display = null;
	        fire = null;
	    }
	}
	
	@Override
	public FXEngineConnector getFX() {
	    return fx;
	}
	
	@Override
	public void setEasing(String easing) throws Easing.EasingNotSupportedException {
	    if(easing==null) return;
	    
		easing = easing.toLowerCase();
		
		if(easing.equals(EASE)) {
			setEasing(0.25, 0.1, 0.25, 1.0);
		}
		else
		if(easing.equals(LINEAR)) {
			this.easing = new LinearEasing();
		}
		else
		if(easing.equals(EASE_IN)) {
			setEasing(0.42, 0, 1.0, 1.0);
		}
		else
		if(easing.equals(EASE_OUT)) {
			setEasing(0, 0, 0.58, 1.0);
		}
		else
		if(easing.equals(EASE_IN_OUT)) {
			setEasing(0.42, 0, 0.58, 1.0);
		}
		else {
		    RegExp exp = RegExp.compile(REGEX_CUBIC_BEZIER);
		    SplitResult result = exp.split(easing);
            
		    if(result.length()!=4)
		    throw new Easing.EasingNotSupportedException();
		        
            setEasing(Double.valueOf(result.get(0)), Double.valueOf(result.get(1)), Double.valueOf(result.get(2)), Double.valueOf(result.get(3)));
		}
	}

    @Override
    public void setEasing(Easing easing) throws Easing.EasingNotSupportedException {
        this.easing = easing;
    }
	
	@Override
	public void setEasing(double x1, double y1, double x2, double y2) {
		this.easing = new CubicBezier(x1, y1, x2, y2);
	}
	
	@Override
    public String getEasing() {
	    if(easing!=null)
        return easing.toString();
	    
	    return null;
    }

	@Override
	public boolean validateChannel(Channel channel) { 
	    if(channel instanceof ChannelCSS3) {
	        return false;
	    }
	    
	    return true;
	}
	
	@Override
	protected void onStart() {
		fx.onStart();
		super.onStart();
	}
	
	@Override
	protected void onComplete() {
		super.onComplete();
		fx.onComplete();
	}

	@Override
	protected void onUpdate(double progress) {
		fx.onUpdate(progress);
	}
	
	@Override
    public void run(int duration) {
	    this.delay = 0;
        this.duration = duration;
        run();
	}
	
	@Override
	public void run(int duration, int delay) {
	    this.delay = delay;
        this.duration = duration;
        run();
	}
	
    @Override
    public void hookFire(int duration) {
        run(duration, Duration.currentTimeMillis());
    }
    
    @Override
    protected double interpolate(double progress) {
        if(easing!=null) {
            return easing.interpolate(progress);
        }
        
        return super.interpolate(progress);
    }
	
	//- - -
    
    protected ChannelContext getContext() {
        return fx.getContext();
    }

    private final void run() {
        if(duration<=0) {
            cancel();
            onStart(); 
            onUpdate(1.0);
            onComplete();
            return;
        }
        
        if(delay>0) {
	        Core.createTimeout(getHandleFire(), delay);
        }
        else {
            fire();
        }
	}
    
    protected final void fire() {
        if(fx.getContext().isShowAndHide()) {
            Scheduler.get().scheduleDeferred(getDisplay());
        }
        else {
            Scheduler.get().scheduleDeferred(getFire());
        }
    }
    
    protected final Scheduler.ScheduledCommand getDisplay() {
        if(display==null) display = new Scheduler.ScheduledCommand() {
            @Override
            public void execute() {
                fx.getContext().setVisible(true);
                Scheduler.get().scheduleDeferred(getFire());
            }
        };
        
        return display;
    }
    
    protected final Scheduler.ScheduledCommand getFire() {
        if(fire==null) fire = new Scheduler.ScheduledCommand() {
            @Override
            public void execute() {
                run(duration, Duration.currentTimeMillis());
            }
        };
        
        return fire;
    }
    
    protected final JavaScriptObject getHandleFire() {
        if(handleFire==null) handleFire = createFireHandle(this);
        
        return handleFire;
    }

    private final static native JavaScriptObject createFireHandle(FXEngineJS engine) /*-{  
        return function() {$entry(engine.@me.l1k3.fx.client.engine.impl.FXEngineJS::fire()());};
    }-*/;
    
    //- - -
    
    @Override
    public String toString() {
        return JS;
    }
}
